/**
* File: Tournament.cpp
*
* \brief Tournament support code.
**/
#include "Tournament.h"
#include <stdio.h>
#include <time.h>
#include "../modApi.h"
#include "../util/Properties.h"
#include "../util/stringLibrary.h"
#include "../util/mathLibrary.h"
#include "../util/ioLibrary.h"
#include "../util/util.h"
using namespace util;
#include "../agents/globals.h"
using namespace agents;
#include "gui.h"
#include "World.h"
using namespace game;
using namespace game::gui;

Tournament game::g_tournament;

/** 
* \brief The class constructor.
**/
Tournament::Tournament()
{
	readyForNewRound = true;
	_currentRound = -1;
}
/** 
* \brief Loads the tournament
* 
* \param[in] file The tournament configuration file.
**/
void Tournament::load(char* file)
{
	CVAR_SET_STRING("mp_autoteambalance", "0");

	Properties properties(file);
	string temp;
	_isOn = util::strings::atob(properties.getProperty("isTournamentOn").c_str());
	_scoreForGoal = atoi(
		properties.getProperty("scoreForGoal").c_str());
	_scorePerKill = atoi(
		properties.getProperty("scorePerKill").c_str());
	_scorePerRemainingSecond = atoi(
		properties.getProperty("scorePerRemainingSecond").c_str());
	_roundCount = atoi(
		properties.getProperty("roundCount").c_str());
	_agentsPerTeam = atoi(
		properties.getProperty("agentsPerTeam").c_str());
}
/** 
* \brief Initializes the tournament. 
*
* Should be called after the 'load' method.
**/
void Tournament::initialize()
{
	if(!isOn())
		return;

	readTeams();
	determineWhetherIsTournamentMaster();
	setupRoundTeams();
}
/** 
* \brief Read team information. 
*
* Reads from the 'plugins.ini' file.
**/
void Tournament::readTeams()
{
	string line, team;
	int indexOfSeparator;
	int i=0;

	char buffer[512];
	char fileName[256];
	util::getGameDirectory(fileName);
	strcat(fileName, "/addons/metamod/plugins.ini");
	FILE* sIn = fopen(fileName, "r");

	while(util::io::getLine(sIn, buffer))
	{
		line = buffer;
		if(line.find_first_of("#") == 0)
			continue;
		if(line.find_first_of("//") == 0)
			continue;
		indexOfSeparator = line.find_first_of(" ");
		team = line.substr(indexOfSeparator + 1, line.size());
		if(i < TOURNAMENT_NUMBER_OF_TEAMS)
			_teams[i].name = team;
		i++;
	}

	fclose(sIn);
}
/** 
* \brief Determines the tournament master. 
*
* It is the first DLL in the 'plugins.ini' file. 
**/
void Tournament::determineWhetherIsTournamentMaster()
{
	char modPath[64];
	strcpy(modPath, "addons/aasmCsAgents");
	strcat(modPath, AASM_GROUP_ID);
	strcat(modPath, "/");
	_isTournamentMaster = false;

	string prefix = _teams[0].name.substr(0, strlen(modPath));
	if(prefix.compare(modPath) == 0)
		_isTournamentMaster = true;
}
/** 
* \brief Selects teams per round.
**/
void Tournament::setupRoundTeams()
{
	int halfRoundCount = util::math::ceiling(_roundCount/2.0);

	for(int i=0; i<halfRoundCount; i++)
	{
		_teams[0].rounds[i].team = CounterStrikeTeams::terrorist;
		_teams[1].rounds[i].team = CounterStrikeTeams::counterTerrorist;
	}
	for(int i=halfRoundCount; i<_roundCount; i++)
	{
		_teams[0].rounds[i].team = CounterStrikeTeams::counterTerrorist;
		_teams[1].rounds[i].team = CounterStrikeTeams::terrorist;
	}
}
/** 
* \brief Should be invoked when a new round starts. 
**/
void Tournament::newRound()
{
	if(!isOn())
		return;

	if(!readyForNewRound)
		return;

	readyForNewRound = false;

	_currentRound++;
	if(_currentRound >= _roundCount)
		conclude();

	int halfRoundCount = util::math::ceiling(_roundCount/2.0);
	if((_currentRound == 0) || (_currentRound == halfRoundCount))
	{
		game::gui::removeAllAgents();

		CounterStrikeTeams::Enumeration teamEnum = _teams[getTeamIndex()].rounds[_currentRound].team;
		char team[2];
		char name[64];

		if(teamEnum == CounterStrikeTeams::terrorist)
			strcpy(team, "1");
		else
			strcpy(team, "2");

		for(int i=0; i<_agentsPerTeam; i++){
			g_agentCreationTabs[i].bNeedsCreation = true; // Needs to be created
			g_agentCreationTabs[i].pCallingEnt = g_world->hostEdict; // Entity who issued it

			if(teamEnum == CounterStrikeTeams::terrorist)
				sprintf(name, "[CH]terrorist%d", i);
			else
				sprintf(name, "[CH]counterTerrorist%d", i);

			strcpy(g_agentCreationTabs[i].name, name);
			strcpy(g_agentCreationTabs[i].team, team);
			strcpy(g_agentCreationTabs[i].model, "-1");
			strcpy(g_agentCreationTabs[i].mind, "tournament");
			
			if (g_agentCreationTime == 0) // Create soon...
				g_agentCreationTime = g_world->getTime();
		}
	}
}
/** 
* \brief Concludes the tournament.
**/
void Tournament::conclude()
{
	if(isTournamentMaster())
		EMIT_SOUND_DYN2(g_world->hostEdict, CHAN_WEAPON, "common/wpn_hudon.wav", 1.0, ATTN_NORM, 0, 100);
	resolveStats();
	saveSummary();
	exit(0);
}
/** 
* \brief Calculates final scores.
**/
void Tournament::resolveStats()
{
	for(int i=0; i<TOURNAMENT_NUMBER_OF_TEAMS; i++)
	{
		_teams[i].score = 0;
		for(int j=0; j<_roundCount; j++)
		{
			_teams[i].rounds[j].score = _teams[i].rounds[j].nEnemyKills * _scorePerKill;
			if(_teams[i].rounds[j].wasObjectiveAchieved){
				_teams[i].rounds[j].score += _teams[i].rounds[j].wasObjectiveAchieved * _scoreForGoal;
				_teams[i].rounds[j].score += _teams[i].rounds[j].remainingTime * _scorePerRemainingSecond;
			}

			_teams[i].score += _teams[i].rounds[j].score;
		}
	}
}
/** 
* \brief Saves the score in txt and xml format.
**/
void Tournament::saveSummary()
{
	if(!isTournamentMaster())
		return;

	char timeFileStr[128];
	char txtTimeStr[256];
	char xmlTimeStr[128];
	char xmlDateStr[128];
    __time64_t ltime;
    struct tm *today;
    _time64( &ltime );
    today = _localtime64( &ltime );
    strftime(timeFileStr, 128, "%d_%b_%y_%H_%M_%S", today );
	strftime(txtTimeStr, 256, "@date %H:%M:%S, %d/%b/%y", today );
	strftime(xmlTimeStr, 256, "%H:%M:%S", today );
	strftime(xmlDateStr, 256, "%d/%b/%y", today );

	char txtFilePath[256]; 
	util::getGameDirectory(txtFilePath);
	strcat(txtFilePath, "/addons/tournament");
	strcat(txtFilePath, timeFileStr);
	strcat(txtFilePath, ".txt");

	FILE* sOut = fopen(txtFilePath, "wb");
	if(sOut == NULL)
		return;
	saveTxtSummary(sOut, txtTimeStr);
	fclose(sOut);

	char xmlFilePath[256]; 
	util::getGameDirectory(xmlFilePath);
	strcat(xmlFilePath, "/addons/tournament");
	strcat(xmlFilePath, timeFileStr);
	strcat(xmlFilePath, ".xml");
	sOut = fopen(xmlFilePath, "wb");
	if(sOut == NULL)
		return;
	saveXmlSummary(sOut, xmlTimeStr, xmlDateStr);
	fclose(sOut);
}
/** 
* \brief Saves the txt score in a file in the 'addons\' directory.
*
* \param[in] sOut The file.
* \param[in] timeStr Time string.
**/
void Tournament::saveTxtSummary(FILE* sOut, char* timeStr)
{
	fprintf(sOut, "**************************************\r\n");
	fprintf(sOut, "Tournament Summary\r\n");
	fprintf(sOut, "%s\r\n", timeStr);
	fprintf(sOut, "**************************************\r\n");

	int winner;
	float winnerScore = -1;
	fprintf(sOut, "FINAL SCORE:\r\n");
	for(int i=0; i<TOURNAMENT_NUMBER_OF_TEAMS; i++)
	{
		if(_teams[i].score > winnerScore){
			winner = i+1;
			winnerScore = _teams[i].score;
		}
		fprintf(sOut, "\t Team #%d ( %s )\t%f\r\n", i+1, _teams[i].name.c_str(), _teams[i].score);
	}
	bool isDraw = true;
	float score = _teams[0].score;
	for(int i=1; i<TOURNAMENT_NUMBER_OF_TEAMS; i++)
		if(_teams[i].score != score)
			isDraw = false;

	if(isDraw)
		fprintf(sOut, "The match is draw.\r\n");
	else
		fprintf(sOut, "Team %d is the winner!\r\n", winner);
	fprintf(sOut, "--------------------------------------\r\n");
	
	fprintf(sOut, "PARTIAL SCORES:\r\n");
	for(int i=0; i<_roundCount; i++)
	{
		fprintf(sOut, " Round #%d\r\n", i+1);
		for(int j=0; j<TOURNAMENT_NUMBER_OF_TEAMS; j++)
		{
			if(_teams[j].rounds[i].team == CounterStrikeTeams::counterTerrorist)
				fprintf(sOut, "\tTeam #%d: (CT)\r\n", j+1);
			else
				fprintf(sOut, "\tTeam #%d: (T)\r\n", j+1);
			fprintf(sOut, "\t nKills: %d\r\n", _teams[j].rounds[i].nEnemyKills);
			if(_teams[j].rounds[i].wasObjectiveAchieved)
			{
				fprintf(sOut, "\t goal?: true\r\n");
				fprintf(sOut, "\t remaining time (s): %f\r\n", _teams[j].rounds[i].remainingTime);
				if(_teams[j].rounds[i].enemyRaisedException)
					fprintf(sOut, "\t enemy raised exception?: true\r\n");
				else
					fprintf(sOut, "\t enemy raised exception?: false\r\n");
			}
			else
				fprintf(sOut, "\t goal?: false\r\n");
			fprintf(sOut, "\t SCORE: %f\r\n", _teams[j].rounds[i].score);
		}
	}
	fprintf(sOut, "--------------------------------------\r\n");

	fprintf(sOut, "RULES:\r\n");
	fprintf(sOut, " Score per kill: %d\r\n", _scorePerKill);
	fprintf(sOut, " Score per Goal: %d\r\n", _scoreForGoal);
	fprintf(sOut, " Score per Remaining Second: %d\r\n", _scorePerRemainingSecond);
	fprintf(sOut, "\r\n");
	fprintf(sOut, " Map: %s\r\n", STRING(gpGlobals->mapname));
	fprintf(sOut, " Round time (minutes): %f\r\n", CVAR_GET_FLOAT("mp_roundtime"));
	fprintf(sOut, " Freeze time (seconds): %f\r\n", CVAR_GET_FLOAT("mp_freezetime"));
	fprintf(sOut, " Buy time (minutes): %f\r\n", CVAR_GET_FLOAT("mp_buytime"));
	if(CVAR_GET_FLOAT("mp_friendlyfire"))
		fprintf(sOut, " Friendly fire: true\r\n");
	else
		fprintf(sOut, " Friendly fire: false\r\n");
	fprintf(sOut, "--------------------------------------\r\n");
	fprintf(sOut, "[END]\r\n");
}
/** 
* \brief Saves the xml score in a file in the 'addons\' directory.
*
* \param[in] sOut The file.
* \param[in] timeStr Time string.
* \param[in] dateStr Date string.
**/
void Tournament::saveXmlSummary(FILE* sOut, char* timeStr, char* dateStr)
{
	fprintf(sOut, "<tournament-summary time='%s' date='%s'>\r\n", timeStr, dateStr);

	fprintf(sOut, " <score>\r\n");
	for(int i=0; i<TOURNAMENT_NUMBER_OF_TEAMS; i++)
		fprintf(sOut, "  <team name='%s' score='%f'/>\r\n", _teams[i].name.c_str(), _teams[i].score);
	fprintf(sOut, " </score>\r\n");
	
	fprintf(sOut, " <partial-scores>\r\n");
	for(int i=0; i<_roundCount; i++)
	{
		fprintf(sOut, " <round id='%d'>\r\n", i+1);
		for(int j=0; j<TOURNAMENT_NUMBER_OF_TEAMS; j++)
		{
			if(_teams[j].rounds[i].team == CounterStrikeTeams::counterTerrorist)
				fprintf(sOut, "   <team name='%s' csTeam='CT'\r\n", _teams[j].name.c_str());
			else
				fprintf(sOut, "   <team name='%s' csTeam='T'\r\n", _teams[j].name.c_str());
			fprintf(sOut, "    nKills='%d'\r\n", _teams[j].rounds[i].nEnemyKills);
			if(_teams[j].rounds[i].wasObjectiveAchieved)
			{
				fprintf(sOut, "    goal='true'\r\n");
				fprintf(sOut, "    remainingTime='%f'\r\n", _teams[j].rounds[i].remainingTime);
				if(_teams[j].rounds[i].enemyRaisedException)
					fprintf(sOut, "    enemyRaisedException='true'\r\n");
				else
					fprintf(sOut, "    enemyRaisedException='false'\r\n");
			}
			else
				fprintf(sOut, "    goal='false'\r\n");
			fprintf(sOut, "    score='%f'/>\r\n", _teams[j].rounds[i].score);
		}
		fprintf(sOut, " </round>\r\n");
	}
	fprintf(sOut, " </partial-scores>\r\n");

	fprintf(sOut, " <rules\r\n");
	fprintf(sOut, "  scorePerKill='%d'\r\n", _scorePerKill);
	fprintf(sOut, "  scorePerGoal='%d'\r\n", _scoreForGoal);
	fprintf(sOut, "  scorePerRemainingSecond='%d'\r\n", _scorePerRemainingSecond);
	fprintf(sOut, "  map='%s'\r\n", STRING(gpGlobals->mapname));
	fprintf(sOut, "  roundTime='%f'\r\n", CVAR_GET_FLOAT("mp_roundtime"));
	fprintf(sOut, "  freezeTime='%f'\r\n", CVAR_GET_FLOAT("mp_freezetime"));
	fprintf(sOut, "  buyTime='%f'\r\n", CVAR_GET_FLOAT("mp_buytime"));
	if(CVAR_GET_FLOAT("mp_friendlyfire"))
		fprintf(sOut, "  friendlyFire='true'\r\n");
	else
		fprintf(sOut, "  friendlyFire='false'\r\n");
	fprintf(sOut, " />\r\n");
	
	fprintf(sOut, "</tournament-summary>\r\n", timeStr);
}
/** 
* \brief Gets the team which is associated to a CS team in the current round.
**/
Team* Tournament::getTeam(CounterStrikeTeams::Enumeration team)
{
	if(_teams[0].rounds[_currentRound].team == team)
		return &_teams[0];
	else
		return &_teams[1];
}

/** 
* \brief A agent has been killed. Score appropriately. 
**/
void Tournament::handleEnemyKill(edict_t* killer, edict_t* victim)
{
	if(!isOn() || !isTournamentMaster())
		return;

	CounterStrikeTeams::Enumeration killerTeam = util::getTeam(killer);
	CounterStrikeTeams::Enumeration victimTeam = util::getTeam(victim);

	if(killerTeam == victimTeam)
		return;

	getTeam(killerTeam)->rounds[_currentRound].nEnemyKills++;
}
/** 
* \brief A team has achieved its goal. Score appropriately. 
**/
void Tournament::handleObjectiveAchieved(CounterStrikeTeams::Enumeration victoriousTeam, bool enemyRaisedException)
{
	if(!isOn() || !isTournamentMaster())
		return;

	if(_ignoreAchievedObjective)
	{
		_ignoreAchievedObjective = false;
		return;
	}

	Team* team = getTeam(victoriousTeam);
	team->rounds[_currentRound].wasObjectiveAchieved = true;
	team->rounds[_currentRound].remainingTime = max(0, g_world->getTimeRoundEnds() - g_world->getTime());
	team->rounds[_currentRound].enemyRaisedException = enemyRaisedException;
}
/** 
* \brief Handles an exception. 
*
* \param[in] mastersException Whether it was a master's exception.
**/
void Tournament::handleException(bool mastersException)
{
	if(!isOn() || !isTournamentMaster())
		return;

	Team* team;
	if(mastersException)
		team = &_teams[!getTeamIndex()];
	else
		team = &_teams[getTeamIndex()];
	
	handleObjectiveAchieved(team->rounds[_currentRound].team, true);
	forceNewRound();
}
/** 
* \brief Forces a new round.
**/
void Tournament::forceNewRound()
{
	_ignoreAchievedObjective = true;
	_forceNewRound = true;
	_timeToStartNewRound = g_world->getTime() + 1;
}
/** 
* \brief Updates the tournament.
*
* Should be invoked once per cycle.
**/
void Tournament::update()
{
	if(!isOn() || !isTournamentMaster())
		return;

	if(_forceNewRound && (g_world->getTime() < _timeToStartNewRound))
	{
		_forceNewRound = false;
		game::gui::newroundAll();
	}
}